﻿// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/* PoolingListBase.cs -- базовый класс для списка, хранящего элементы в пуле
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Buffers;
using System.Collections.Generic;

#endregion

#nullable enable

namespace AM.Memory.Collections.Specialized;

/// <summary>
/// Базовый класс для списка, хранящего свои элементы в массиве,
/// взятом из пула.
/// </summary>
/// <typeparam name="T">Тип элементов списка.</typeparam>
public abstract class PoolingListBase<T>
    : IDisposable,
    IPoolingEnumerable<T>
{
    #region Private members

    /// <summary>
    /// Указатель на корневую ноду.
    /// </summary>
    protected IMemoryOwner<IPoolingNode<T>> _root = default!;

    /// <summary>
    /// Количество элементов.
    /// </summary>
    protected int _count;

    /// <summary>
    /// Версия.
    /// </summary>
    protected int _ver;

    /// <summary>
    /// Создание холдера для ноды.
    /// </summary>
    /// <returns></returns>
    protected abstract IPoolingNode<T> CreateNodeHolder();

    #endregion

    #region Public methods


    /// <summary>
    /// Добавление элемента в конец списка.
    /// </summary>
    public void Add (T item)
    {
        var bn = _count >> PoolsDefaults.DefaultPoolBucketDegree;
        var bi = _count & PoolsDefaults.DefaultPoolBucketMask;

        _root.Memory.Span[bn] ??= CreateNodeHolder();

        _root.Memory.Span[bn][bi] = item;

        _count++;

        unchecked
        {
            _ver++;
        }
    }

    /// <inheritdoc cref="ICollection{T}.Clear"/>
    public void Clear()
    {
        for (int i = 0, len = _root.Memory.Span.Length; i < len; i++)
        {
            if (_root.Memory.Span[i] == null!)
            {
                break;
            }

            _root.Memory.Span[i].Clear();
            _root.Memory.Span[i].Dispose();
            _root.Memory.Span[i] = default!;
        }

        _count = default;

        unchecked
        {
            _ver++;
        }
    }

    /// <inheritdoc cref="ICollection{T}.Count"/>
    public bool Contains
        (
            T item
        )
    {
        return IndexOf (item) != -1;
    }

    /// <inheritdoc cref="ICollection{T}.CopyTo"/>
    public void CopyTo
        (
            T[] array,
            int arrayIndex
        )
    {
        Sure.NotNull (array);
        Sure.Positive (arrayIndex);

        var len = 0;
        for (var i = 0; i <= PoolsDefaults.DefaultPoolBucketSize; i++)
        for (var j = 0; j < PoolsDefaults.DefaultPoolBucketSize && len < _count; j++, len++)
        {
            array[len] = _root.Memory.Span[i][j];
        }
    }

    /// <summary>
    ///
    /// </summary>
    public int Count => _count;

    /// <inheritdoc cref="ICollection{T}.Remove"/>
    public bool Remove (T item)
    {
        int i, j;
        for (i = 0, j = 0; i < _count; i++)
        {
            var bfn = i >> PoolsDefaults.DefaultPoolBucketDegree;
            var bfi = i & PoolsDefaults.DefaultPoolBucketMask;
            var btn = j >> PoolsDefaults.DefaultPoolBucketDegree;
            var bti = j & PoolsDefaults.DefaultPoolBucketMask;

            if (!_root.Memory.Span[bfn][bfi]!.Equals (item))
            {
                _root.Memory.Span[btn][bti] = _root.Memory.Span[bfn][bfi];
                j++;
            }
            else
            {
                _count--;
            }
        }

        unchecked
        {
            _ver++;
        }

        return i != j && i != 0;
    }

    /// <inheritdoc cref="ICollection{T}.IsReadOnly"/>
    public bool IsReadOnly => false;

    /// <summary>
    /// Поиск элемента в списке.
    /// </summary>
    public int IndexOf (T item)
    {
        var len = 0;

        for (var i = 0; i <= PoolsDefaults.DefaultPoolBucketSize; i++)
        for (var j = 0; j < PoolsDefaults.DefaultPoolBucketSize && len < _count; j++, len++)
        {
            if (item!.Equals (_root.Memory.Span[i][j]))
            {
                return len;
            }
        }

        return -1;
    }

    /// <summary>
    /// Вставка элемента по указанному индексу.
    /// </summary>
    public void Insert (int index, T item)
    {
        if (index < _count)
        {
            throw new IndexOutOfRangeException (nameof (index));
        }

        for (var i = index; i <= _count; i++)
        {
            var j = i + 1;

            var bn = i >> PoolsDefaults.DefaultPoolBucketDegree;
            var bi = i & PoolsDefaults.DefaultPoolBucketMask;

            var bjn = j >> PoolsDefaults.DefaultPoolBucketDegree;
            var bji = j & PoolsDefaults.DefaultPoolBucketMask;

            var copy = _root.Memory.Span[bn][bi];
            _root.Memory.Span[bjn][bji] = item;
            item = copy;
        }

        _count++;
        unchecked
        {
            _ver++;
        }
    }

    /// <summary>
    /// Удаление элемента по указанному индексу.
    /// </summary>
    public void RemoveAt (int index)
    {
        if (index >= _count)
        {
            throw new IndexOutOfRangeException (nameof (index));
        }

        for (int i = index, j = i + 1; i <= _count; i++)
        {
            var bn = i >> PoolsDefaults.DefaultPoolBucketDegree;
            var bi = i & PoolsDefaults.DefaultPoolBucketMask;

            var bjn = j >> PoolsDefaults.DefaultPoolBucketDegree;
            var bji = j & PoolsDefaults.DefaultPoolBucketMask;

            _root.Memory.Span[bn][bi] = _root.Memory.Span[bjn][bji];
        }

        _count--;
        unchecked
        {
            _ver++;
        }
    }

    /// <summary>
    /// Изменение размера списка.
    /// </summary>
    public void Resize (int size)
    {
        if (size == _count) return;
        if (size < _count)
        {
            var cbn = _count >> PoolsDefaults.DefaultPoolBucketDegree;
            var sbn = size >> PoolsDefaults.DefaultPoolBucketDegree;
            var sbi = size & PoolsDefaults.DefaultPoolBucketMask;

            for (var bn = sbn + 1; bn <= cbn; bn++)
            {
                _root.Memory.Span[bn].Dispose();
                _root.Memory.Span[bn] = default!;
            }

            var span = _root.Memory.Span[sbn];
            for (var i = sbi; i <= PoolsDefaults.DefaultPoolBucketSize; i++)
            {
                span[i] = default!;
            }

            _count = size;
        }
        else
        {
            var cbn = _count >> PoolsDefaults.DefaultPoolBucketDegree;
            var sbn = size >> PoolsDefaults.DefaultPoolBucketDegree;

            for (var bn = cbn + 1; bn <= sbn; bn++)
            {
                _root.Memory.Span[bn] = CreateNodeHolder();
            }

            _count = size;
        }
    }

    /// <summary>
    /// Доступ по индексу.
    /// </summary>
    public T this [int index]
    {
        get
        {
            if (index >= _count)
            {
                throw new IndexOutOfRangeException (nameof (index));
            }

            var bn = index >> PoolsDefaults.DefaultPoolBucketDegree;
            var bi = index & PoolsDefaults.DefaultPoolBucketMask;
            return _root.Memory.Span[bn][bi];
        }
        set
        {
            if (index >= _count)
            {
                throw new IndexOutOfRangeException (nameof (index));
            }

            var bn = index >> PoolsDefaults.DefaultPoolBucketDegree;
            var bi = index & PoolsDefaults.DefaultPoolBucketMask;
            _root.Memory.Span[bn][bi] = value;

            unchecked
            {
                _ver++;
            }
        }
    }

    #endregion

    #region IPoolingEnumerable members

    /// <inheritdoc cref="IPoolingEnumerable{T}.GetEnumerator"/>
    public IPoolingEnumerator<T> GetEnumerator()
    {
        return Pool<Enumerator>.Get().Init (this);
    }

    IPoolingEnumerator IPoolingEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion

    /// <inheritdoc cref="IDisposable.Dispose"/>
    public void Dispose()
    {
        Clear();
        _root?.Dispose();
        _root = default!;
    }

    private class Enumerator
        : IPoolingEnumerator<T>
    {
        private PoolingListBase<T> _src = default!;
        private int _bucket, _index, _ver;

        public Enumerator Init (PoolingListBase<T> src)
        {
            _bucket = 0;
            _index = -1;
            _src = src;
            _ver = _src._ver;
            return this;
        }

        public bool MoveNext()
        {
            if (_index >= _src.Count) return false;
            if (_ver != _src._ver)
            {
                throw new InvalidOperationException ("Collection was changed while enumeration");
            }

            _index++;
            var tb = _src._count >> PoolsDefaults.DefaultPoolBucketDegree;
            var ti = _src._count & PoolsDefaults.DefaultPoolBucketMask;

            if (_index == PoolsDefaults.DefaultPoolBucketSize)
            {
                _index = 0;
                _bucket++;
            }

            if ((_bucket < tb && _index < PoolsDefaults.DefaultPoolBucketSize) ||
                (_bucket == tb && _index < ti))
            {
                return true;
            }

            return false;
        }

        public void Reset()
        {
            _index = -1;
            _bucket = 0;
            _ver = _src._ver;
        }

        public T Current
        {
            get
            {
                if (_ver != _src._ver)
                {
                    throw new InvalidOperationException ("Collection was changed while enumeration");
                }

                return _src._root.Memory.Span[_bucket][_index];
            }
        }

        object IPoolingEnumerator.Current => Current!;

        /// <inheritdoc cref="IDisposable.Dispose"/>
        public void Dispose()
        {
            Pool<Enumerator>.Return (this);
        }
    }
}
